using System;
using Microsoft.SPOT;
using System.IO.Ports;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Collections;
using System.IO;


namespace ntools.NetduinoGo.PlusModule.NetduinoPlus
{
    public class Link
    {
        private GoBus.GoBusSerialTransport transport;

        private Socket[] sockets = new Socket[10];
        private NetworkStream[] networkstreams = new NetworkStream[4];
        private FileStream[] filestreams = new FileStream[4];
        private IEnumerable[] enumerables = new IEnumerable[4];
        private IEnumerator[] enumerators = new IEnumerator[4];

        public Link()
        {
            // configure our UUID
            byte[] _myUUID = new byte[] { 51, 110, 49, 191, 93, 161, 15, 70, 150, 200, 98, 43, 56, 61, 114, 23 };

            transport = new GoBus.GoBusSerialTransport("COM2", _myUUID);
            transport.FunctionCallReceived += new GoBus.GoBusSerialTransport.FunctionCallEventHandler(FunctionCall);
            transport.ResetRequestReceived += new GoBus.GoBusSerialTransport.EventHandler(ResetRequest);

            Debug.Print("Started link");
        }

        private void ResetRequest(object sender, GoBus.GoBusSerialTransport.EventArgs e)
        {
            //throw new NotImplementedException();
        }

        private byte[] FunctionCall(object sender, GoBus.GoBusSerialTransport.GoBusFunctionEventArgs e)
        {
            try
            {
                if (e.instance == INSTANCE_MAIN)
                {
                    switch (e.function)
                    {
                        case COMMAND_MAIN_CREATESOCKET:

                            for (int i = 0; i < sockets.Length; i++)
                                if (sockets[i] == null)
                                {
                                    sockets[i] = new Socket((AddressFamily)e.parameters[0], (SocketType)e.parameters[1], (ProtocolType)e.parameters[2]);
                                    return new byte[] { (byte)i };
                                }

                            break;

                        case COMMAND_MAIN_PING:

                            if (Program.NetworkAvailable)
                                return Encoding.UTF8.GetBytes("PONG " + Program.IP);
                            else
                                return Encoding.UTF8.GetBytes("PONG");

                            break;

                        case COMMAND_MAIN_CREATENETWORKSTREAM:

                            for (int i = 0; i < this.networkstreams.Length; i++)
                                if (this.networkstreams[i] == null)
                                {
                                    this.networkstreams[i] = new NetworkStream(this.sockets[e.parameters[0]]);
                                    return new byte[] { (byte)i };
                                }

                            break;

                        case COMMAND_MAIN_CREATEFILESTREAM:

                            byte[] buffer = new byte[e.parameters.Length - 1];
                            Array.Copy(e.parameters, 1, buffer, 0, buffer.Length);

                            for (int i = 0; i < this.filestreams.Length; i++)
                                if (this.filestreams[i] == null)
                                {
                                    this.filestreams[i] = new FileStream(new string(System.Text.Encoding.UTF8.GetChars(buffer)), (FileMode)(int)e.parameters[0]);
                                    return new byte[] { (byte)i };
                                }
                            break;


                    }
                }

                else if (e.instance == INSTANCE_SOCKET)
                {
                    byte[] buffer;

                    switch (e.function)
                    {

                        case COMMAND_SOCKET_SETSOCKETOPTION:
                            this.sockets[e.parameters[0]].SetSocketOption((SocketOptionLevel)e.parameters[1], (SocketOptionName)e.parameters[2], ((int)e.parameters[3] == 1));
                            return new byte[] { 1 };
                            break;

                        case COMMAND_SOCKET_SENDTIMEOUT:
                            if (e.parameters.Length == 2)
                                this.sockets[e.parameters[0]].SendTimeout = e.parameters[1];

                            return new byte[] { (byte)this.sockets[e.parameters[0]].SendTimeout };
                            break;

                        case COMMAND_SOCKET_SEND:
                            buffer = new byte[e.parameters.Length - 1];
                            Array.Copy(e.parameters, 1, buffer, 0, buffer.Length);
                            this.sockets[e.parameters[0]].Send(buffer);
                            return new byte[] { 1 };
                            break;

                        case COMMAND_SOCKET_DISPOSE:
                            this.sockets[e.parameters[0]].Close();
                            this.sockets[e.parameters[0]] = null;
                            return new byte[] { 1 };
                            break;

                        case COMMAND_SOCKET_CONNECT:

                            buffer = new byte[e.parameters.Length - 2];
                            Array.Copy(e.parameters, 2, buffer, 0, buffer.Length);
                            this.sockets[e.parameters[0]].Connect(new IPEndPoint(new IPAddress(buffer), e.parameters[1]));
                            return new byte[] { 1 };
                            break;

                        case COMMAND_SOCKET_BIND:
                            buffer = new byte[e.parameters.Length - 2];
                            Array.Copy(e.parameters, 2, buffer, 0, buffer.Length);
                            this.sockets[e.parameters[0]].Bind(new IPEndPoint(new IPAddress(buffer), e.parameters[1]));
                            return new byte[] { 1 };
                            break;

                        case COMMAND_SOCKET_LISTEN:
                            this.sockets[e.parameters[0]].Listen(e.parameters[1]);
                            return new byte[] { 1 };
                            break;

                        case COMMAND_SOCKET_POLL:
                            if (this.sockets[e.parameters[0]].Poll(e.parameters[1], (SelectMode)e.parameters[2]))
                                return new byte[] { 1 };
                            else
                                return new byte[] { 0 };
                            break;


                        case COMMAND_SOCKET_ACCEPT:
                            for (int i = 0; i < this.sockets.Length; i++)
                                if (this.sockets[i] == null)
                                {
                                    this.sockets[i] = this.sockets[e.parameters[0]].Accept();
                                    return new byte[] { (byte)i };
                                }
                            break;

                        case COMMAND_SOCKET_AVAILABLE:
                            return new byte[] { (byte)this.sockets[e.parameters[0]].Available };
                            break;

                        case COMMAND_SOCKET_RECEIVE:
                            buffer = new byte[e.parameters[1]];
                            int read = this.sockets[e.parameters[0]].Receive(buffer, e.parameters[1], (SocketFlags)e.parameters[2]);                            
                            if (read == buffer.Length)
                                return buffer;
                            else
                            {
                                byte[] buffer2 = new byte[read];
                                Array.Copy(buffer, buffer2, read);
                                return buffer2;
                            }
                            break;

                        case COMMAND_SOCKET_REMOTEENDPOINT:
                            var endpoint = this.sockets[e.parameters[0]].RemoteEndPoint as IPEndPoint;
                            byte[] address = endpoint.Address.GetAddressBytes();
                            buffer = new byte[address.Length + 1];
                            buffer[0] = (byte)endpoint.Port;
                            Array.Copy(address, 0, buffer, 1, address.Length);
                            address = null;
                            return buffer;
                            break;
                    }
                }

                else if (e.instance == INSTANCE_NETWORKSTREAM)
                {
                    switch (e.function)
                    {
                        case COMMAND_NETWORKSTREAM_READBYTE:
                            return new byte[] { (byte)this.networkstreams[e.parameters[0]].ReadByte() };
                            break;

                        case COMMAND_NETWORKSTREAM_READ:
                            byte[] buffer = new byte[e.parameters[1]];
                            int read = this.networkstreams[e.parameters[0]].Read(buffer, 0, buffer.Length);
                            if (read == buffer.Length)
                                return buffer;
                            else
                            {
                                byte[] buffer2 = new byte[read];
                                Array.Copy(buffer, buffer2, read);
                                return buffer2;
                            }
                            break;

                        case COMMAND_NETWORKSTREAM_DISPOSE:
                            this.networkstreams[e.parameters[0]].Dispose();
                            this.networkstreams[e.parameters[0]] = null;
                            return new byte[] { 1 };
                            break;

                        case COMMAND_NETWORKSTREAM_CANREAD:
                            return new byte[] { (this.networkstreams[e.parameters[0]].CanRead) ? (byte)0 : (byte)1 };
                            break;
                    }
                }

                else if (e.instance == INSTANCE_DNS)
                {
                    switch (e.function)
                    {
                        case COMMAND_DNS_GETHOSTENTRY:
                            return Dns.GetHostEntry(new string(System.Text.Encoding.UTF8.GetChars(e.parameters))).AddressList[0].GetAddressBytes();
                            break;
                    }
                }


                else if (e.instance == INSTANCE_FILESTREAM)
                {
                    switch (e.function)
                    {
                        case COMMAND_FILESTREAM_WRITE:
                            byte[] buff = new byte[e.parameters.Length - 3];
                            Array.Copy(e.parameters, 3, buff, 0, buff.Length);
                            this.filestreams[e.parameters[0]].Write(buff, e.parameters[1], e.parameters[2]);
                            return new byte[] { 1 };
                            break;

                        case COMMAND_FILESTREAM_READ:
                            byte[] buffer = new byte[e.parameters[1]];
                            this.filestreams[e.parameters[0]].Read(buffer, 0, buffer.Length);
                            return buffer;
                            break;

                        case COMMAND_FILESTREAM_POSITION:
                            if (e.parameters.Length == 2)
                                this.filestreams[e.parameters[0]].Position = e.parameters[1];
                            return new byte[] { (byte)this.filestreams[e.parameters[0]].Position };
                            break;

                        case COMMAND_FILESTREAM_LENGTH:
                            return new byte[] { (byte)this.filestreams[e.parameters[0]].Length };
                            break;

                        case COMMAND_FILESTREAM_SEEK:
                            return new byte[] { (byte)this.filestreams[e.parameters[0]].Seek(e.parameters[1], (SeekOrigin)e.parameters[2]) };
                            break;

                        case COMMAND_FILESTREAM_SETLENGTH:
                            this.filestreams[e.parameters[0]].SetLength(e.parameters[1]);
                            return new byte[] { e.parameters[1] };
                            break;

                        case COMMAND_FILESTREAM_FLUSH:
                            this.filestreams[e.parameters[0]].Flush();
                            return new byte[] { 1 };
                            break;

                        case COMMAND_FILESTREAM_CANWRITE:
                            return new byte[] { (this.filestreams[e.parameters[0]].CanWrite) ? (byte)1 : (byte)0 };
                            break;

                        case COMMAND_FILESTREAM_CANSEEK:
                            return new byte[] { this.filestreams[e.parameters[0]].CanSeek ? (byte)1 : (byte)0 };
                            break;

                        case COMMAND_FILESTREAM_CANREAD:
                            return new byte[] { (this.filestreams[e.parameters[0]].CanRead) ? (byte)1 : (byte)0 };
                            break;

                        case COMMAND_FILESTREAM_DISPOSE:
                            this.filestreams[e.parameters[0]].Dispose();
                            this.filestreams[e.parameters[0]] = null;
                            return new byte[] { 1 };

                    }
                }

                else if (e.instance == INSTANCE_FILE)
                {
                    switch (e.function)
                    {
                        case COMMAND_FILE_DELETE:
                            if (File.Exists(new string(System.Text.Encoding.UTF8.GetChars(e.parameters))))
                                File.Delete(new string(System.Text.Encoding.UTF8.GetChars(e.parameters)));
                            return new byte[] { 1 };

                            break;

                        case COMMAND_FILE_EXISTS:
                            if (File.Exists(new string(System.Text.Encoding.UTF8.GetChars(e.parameters))))
                                return new byte[] { 1 };
                            else
                                return new byte[] { 0 };
                            break;
                    }
                }

                else if (e.instance == INSTANCE_DIRECTORY)
                {
                    switch (e.function)
                    {
                        case COMMAND_DIRECTORY_CREATE:
                            Directory.CreateDirectory(new string(System.Text.Encoding.UTF8.GetChars(e.parameters)));
                            return new byte[] { 1 };
                            break;

                        case COMMAND_DIRECTORY_GETENUMERATOR:
                            for (int i = 0; i < this.enumerators.Length; i++)
                                if (this.enumerators[i] == null)
                                {
                                    this.enumerators[i] = Directory.EnumerateFiles(new string(System.Text.Encoding.UTF8.GetChars(e.parameters))).GetEnumerator();
                                    return new byte[] { (byte)i };
                                }
                            break;

                    }
                }

                else if (e.instance == INSTANCE_ENUMERATOR)
                {
                    switch (e.function)
                    {
                        case COMMAND_ENUMERATOR_MOVENEXT:
                            if (this.enumerators[e.parameters[0]].MoveNext())
                                return new byte[] { 1 };
                            return new byte[] { 0 };
                            break;

                        case COMMAND_ENUMERATOR_CURRENT:
                            return System.Text.Encoding.UTF8.GetBytes((string)this.enumerators[e.parameters[0]].Current);
                            break;

                        case COMMAND_ENUMERATOR_RESET:
                            this.enumerators[e.parameters[0]].Reset();
                            return new byte[] { 1 };
                            break;
                    }
                }


                throw new Exception();

            }

            catch (Exception ex)
            {
                Debug.Print("--------------------------------------------------------");
                Debug.Print(ex.Message);
                Debug.Print("instance: " + e.instance + ", command: " + e.function + ", parameters: " + new string(System.Text.Encoding.UTF8.GetChars(e.parameters)));
                Debug.Print(ex.StackTrace);

                throw new Exception();
            }
        }


        public const byte INSTANCE_MAIN = 0x00;
        public const byte COMMAND_MAIN_CREATESOCKET = 0x01;
        public const byte COMMAND_MAIN_PING = 0x02;
        public const byte COMMAND_MAIN_CREATENETWORKSTREAM = 0x03;
        public const byte COMMAND_MAIN_CREATEFILESTREAM = 0x04;

        public const byte INSTANCE_SOCKET = 0x02;
        public const byte COMMAND_SOCKET_SETSOCKETOPTION = 0x01;
        public const byte COMMAND_SOCKET_SENDTIMEOUT = 0x02;
        public const byte COMMAND_SOCKET_SEND = 0x03;
        public const byte COMMAND_SOCKET_DISPOSE = 0x04;
        public const byte COMMAND_SOCKET_CONNECT = 0x05;
        public const byte COMMAND_SOCKET_BIND = 0x06;
        public const byte COMMAND_SOCKET_LISTEN = 0x07;
        public const byte COMMAND_SOCKET_POLL = 0x08;
        public const byte COMMAND_SOCKET_ACCEPT = 0x09;
        public const byte COMMAND_SOCKET_AVAILABLE = 0x10;
        public const byte COMMAND_SOCKET_RECEIVE = 0x11;
        public const byte COMMAND_SOCKET_REMOTEENDPOINT = 0x12;

        public const byte INSTANCE_NETWORKSTREAM = 0x03;
        public const byte COMMAND_NETWORKSTREAM_READBYTE = 0x01;
        public const byte COMMAND_NETWORKSTREAM_READ = 0x02;
        public const byte COMMAND_NETWORKSTREAM_DISPOSE = 0x03;
        public const byte COMMAND_NETWORKSTREAM_CANREAD = 0x04;

        public const byte INSTANCE_DNS = 0x04;
        public const byte COMMAND_DNS_GETHOSTENTRY = 0x01;

        public const byte INSTANCE_FILESTREAM = 0x05;
        public const byte COMMAND_FILESTREAM_READ = 0x01;
        public const byte COMMAND_FILESTREAM_WRITE = 0x02;
        public const byte COMMAND_FILESTREAM_POSITION = 0x03;
        public const byte COMMAND_FILESTREAM_LENGTH = 0x04;
        public const byte COMMAND_FILESTREAM_SEEK = 0x05;
        public const byte COMMAND_FILESTREAM_SETLENGTH = 0x06;
        public const byte COMMAND_FILESTREAM_CANSEEK = 0x07;
        public const byte COMMAND_FILESTREAM_CANREAD = 0x08;
        public const byte COMMAND_FILESTREAM_CANWRITE = 0x09;
        public const byte COMMAND_FILESTREAM_DISPOSE = 0x10;
        public const byte COMMAND_FILESTREAM_FLUSH = 0x11;

        public const byte INSTANCE_FILE = 0x06;
        public const byte COMMAND_FILE_DELETE = 0x01;
        public const byte COMMAND_FILE_EXISTS = 0x02;

        public const byte INSTANCE_DIRECTORY = 0x07;
        public const byte COMMAND_DIRECTORY_CREATE = 0x01;
        public const byte COMMAND_DIRECTORY_GETENUMERATOR = 0x02;

        public const byte INSTANCE_ENUMERATOR = 0x08;
        public const byte COMMAND_ENUMERATOR_MOVENEXT = 0x01;
        public const byte COMMAND_ENUMERATOR_CURRENT = 0x02;
        public const byte COMMAND_ENUMERATOR_RESET = 0x03;
        public const byte COMMAND_ENUMERATOR_DISPOSE = 0x04;


        private string[] ParseParameters(byte[] bytes)
        {
            string parameters = new string(Encoding.UTF8.GetChars(bytes));
            return parameters.Split('|');
        }
    }
}
